package xyz.scootaloo.kami.cloud.service.impl

import cn.hutool.core.codec.Base64
import cn.hutool.core.util.CharsetUtil
import cn.hutool.core.util.StrUtil
import cn.hutool.crypto.asymmetric.KeyType
import cn.hutool.crypto.asymmetric.RSA
import io.vertx.core.Future
import xyz.scootaloo.kami.cloud.lang.Bus
import xyz.scootaloo.kami.cloud.lang.Constant
import xyz.scootaloo.kami.cloud.lang.EventLoop
import xyz.scootaloo.kami.cloud.lang.serializer
import xyz.scootaloo.kami.cloud.model.core.SC
import xyz.scootaloo.kami.cloud.model.po.EncodeToken
import xyz.scootaloo.kami.cloud.model.po.ServiceResult
import xyz.scootaloo.kami.cloud.model.po.TokenContentDecodeBlock
import xyz.scootaloo.kami.cloud.model.po.TokenGenBlock
import xyz.scootaloo.kami.cloud.service.CacheService
import xyz.scootaloo.kami.cloud.service.TokenService
import xyz.scootaloo.kami.cloud.util.Random
import xyz.scootaloo.kami.cloud.util.notNull

/**
 * @author flutterdash@qq.com
 * @since 2022/4/13 10:01
 */
object TokenServiceImpl : TokenService {

    private val cacheService = CacheService().internal()

    override fun generateToken(ipAddress: String, expiryTime: Long): Future<EncodeToken> {
        return Bus.request<EncodeToken>(
            Constant.EB_TOKEN_GEN, serializer(TokenGenBlock(ipAddress, expiryTime))
        ).notNull()
    }

    override fun decodeContent(
        ipAddress: String, clientId: String, base64encoded: String
    ): Future<ServiceResult<String>> {
        return Bus.callService(
            Constant.EB_TOKEN_DEC, serializer(
                TokenContentDecodeBlock(ipAddress, clientId, base64encoded)
            )
        )
    }

    override fun internal(): TokenService.InternalTokenServiceApi {
        return InternalTokenServiceImpl
    }

    object InternalTokenServiceImpl : TokenService.InternalTokenServiceApi {
        override fun link(): TokenService = TokenServiceImpl

        @EventLoop(Constant.CTX_STATE)
        override fun generateToken(ipAddress: String, expiryTime: Long): EncodeToken {
            val rsa = RSA()
            val clientId = generateClientIdNoDuplicate(ipAddress)
            cacheService.put(buildStoreKey(ipAddress, clientId), rsa.privateKeyBase64, expiryTime)
            return EncodeToken(clientId, rsa.publicKeyBase64)
        }

        @EventLoop(Constant.CTX_STATE)
        override fun decodeContent(
            ipAddress: String, clientId: String, base64encoded: String
        ): ServiceResult<String> {
            val cacheKey = buildStoreKey(ipAddress, clientId)
            val base64privateKey = cacheService.get<String>(cacheKey)
            base64privateKey ?: return ServiceResult.error(SC.SYSTEM_CACHE_INVALID)

            // decode

            val encoded = Base64.decode(base64encoded)
            val rsa = RSA(base64privateKey, null)
            val decoded = StrUtil.str(rsa.decrypt(encoded, KeyType.PrivateKey), CharsetUtil.CHARSET_UTF_8)
            return ServiceResult(SC.SUCCEED, decoded)
        }

        private fun generateClientIdNoDuplicate(ipAddress: String): String {
            var clientId = Random.randomString()
            while (cacheService.get<String>(buildStoreKey(ipAddress, clientId)) != null) {
                clientId = Random.randomString()
            }
            return clientId
        }

        private fun buildStoreKey(ipAddress: String, clientId: String): String {
            return "token:$ipAddress:$clientId"
        }
    }
}

